package app

import (
	"errors"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"golang.org/x/sync/errgroup"

	"golang.org/x/net/context"
)

type App struct {
	options
	ctx    context.Context
	cancel func()
}

func NewApp(opts ...Option) *App {
	o := options{
		sigs: []os.Signal{syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT},
	}
	for _, opt := range opts {
		opt(&o)
	}
	ctx, cancel := context.WithCancel(context.Background())
	return &App{
		ctx:     ctx,
		cancel:  cancel,
		options: o,
	}
}

func (a *App) Run() error {
	group, ctx := errgroup.WithContext(a.ctx)
	for _, server := range a.servers {
		server := server
		group.Go(func() (err error) {
			go func() {
				<-ctx.Done()
				c, cancel := context.WithTimeout(context.Background(), time.Second*5)
				defer cancel()
				err = server.Stop(c)
			}()
			return server.Start(ctx)
		})
	}
	group.Go(func() error {
		ch := make(chan os.Signal, 1)
		signal.Notify(ch, a.sigs...)
		for {
			select {
			case <-ctx.Done():
				return ctx.Err()
			case <-ch:
				if a.cancel != nil {
					a.cancel()
					return nil
				}
			}
		}
	})
	if err := group.Wait(); err != nil && !errors.Is(err, context.Canceled) && !errors.Is(err, http.ErrServerClosed) {
		return err
	}
	return nil
}
